import tkinter as tk
import pyautogui
import threading
import time
import keyboard
import json
import random


class ClickerApp:
    def __init__(self, root):
        self.root = root
        self.root.title("Auto Clicker")

        self.lock = threading.Lock()

        '''
        历史配置
        '''
        # 历史配置列表框架
        self.frame_history = tk.Frame(root)
        self.frame_history.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)

        # 历史配置列表
        self.history_listbox = tk.Listbox(self.frame_history, selectmode=tk.SINGLE)
        self.history_listbox.pack(side=tk.TOP, fill=tk.BOTH, expand=True)

        # 滚动条
        self.scrollbar = tk.Scrollbar(self.history_listbox, orient=tk.VERTICAL)
        self.scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
        self.history_listbox.config(yscrollcommand=self.scrollbar.set)
        self.scrollbar.config(command=self.history_listbox.yview)

        # 加载历史配置按钮
        self.btn_load_history = tk.Button(self.frame_history, text="加载历史配置", command=self.load_selected_history)
        self.btn_load_history.pack(side=tk.LEFT, anchor=tk.S, pady=5)

        # 删除历史配置按钮
        self.btn_delete_history = tk.Button(self.frame_history, text="删除历史配置", command=self.delete_selected_history)
        self.btn_delete_history.pack(side=tk.LEFT, anchor=tk.S, pady=5)

        '''
            当前配置
        '''
        # 当前配置框架
        self.frame_current = tk.Frame(root)
        self.frame_current.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True)

        # 当前配置信息描述框
        self.current_info_frame = tk.Frame(self.frame_current)
        self.current_info_frame.pack(side=tk.TOP, fill=tk.X)

        self.label_current_info = tk.Label(self.current_info_frame, text="文件名:", )
        self.label_current_info.pack(side=tk.LEFT, padx=5, pady=5)

        self.entry_current_description = tk.Entry(self.current_info_frame, width=50)
        self.entry_current_description.pack(side=tk.LEFT, padx=5, pady=5)

        # 保存当前配置按钮
        self.btn_save_current = tk.Button(self.current_info_frame, text="保存 (Ctrl+S)",
                                          command=self.save_current_configuration)
        self.btn_save_current.pack(side=tk.LEFT, padx=5, pady=5)

        # 提示标签框架
        self.frame_info = tk.Frame(self.frame_current)
        self.frame_info.pack(side=tk.TOP, fill=tk.X)

        # 提示标签
        self.label_info_get_position = tk.Label(self.frame_info, text="按下 Ctrl+M 获取当前鼠标位置")
        self.label_info_get_position.pack(side=tk.LEFT, padx=5, pady=5)

        # 停止提示标签
        self.label_info_stop_all = tk.Label(self.frame_info, text="按 esc 可停止所有", fg="red")
        self.label_info_stop_all.pack(side=tk.LEFT, padx=5, pady=5)



        # 按钮框架
        self.frame_current_buttons = tk.Frame(self.frame_current)
        self.frame_current_buttons.pack(fill=tk.X)



        # 点击位置列表
        self.position_entries = []
        self.position_threads = []
        self.stop_threads = False  # 控制线程停止的标志

        # 添加按钮
        self.btn_add_position = tk.Button(self.frame_current_buttons, text="新增点击设置 (Ctrl+N)",
                                          command=self.add_position_row)
        self.btn_add_position.pack(side=tk.LEFT, padx=5, pady=5)

        self.btn_start_all = tk.Button(self.frame_current_buttons, text="开始所有 (Ctrl+R)", command=self.start_all_clicking)
        self.btn_start_all.pack(side=tk.LEFT, padx=5, pady=5)

        self.btn_stop_all = tk.Button(self.frame_current_buttons, text="停止所有 (esc)", command=self.stop_all_threads,
                                      state=tk.DISABLED)
        self.btn_stop_all.pack(side=tk.LEFT, padx=5, pady=5)

        # 错误消息
        self.error_label = tk.Label(self.frame_current, text="", fg="red")
        self.error_label.pack()

        # 定时保存当前配置
        self.auto_save_timer()

        # 监听键盘事件
        keyboard.add_hotkey("Ctrl+M", self.get_mouse_position)
        keyboard.add_hotkey("Ctrl+S", self.save_current_configuration)
        keyboard.add_hotkey("Ctrl+N", self.add_position_row)
        keyboard.add_hotkey("Ctrl+R", self.start_all_clicking)
        keyboard.add_hotkey("esc", self.stop_all_threads)

        # 加载历史配置信息
        self.load_configurations_from_file()
        self.load_history_to_listbox()

    def add_position_row(self):
        position_frame = tk.Frame(self.frame_current)
        position_frame.pack(fill=tk.X)

        label_description = tk.Label(position_frame, text="描述:")
        label_description.pack(side=tk.LEFT, padx=5, pady=5)

        entry_description = tk.Entry(position_frame, width=30)
        entry_description.pack(side=tk.LEFT, padx=5, pady=5)

        label_position = tk.Label(position_frame, text="位置 (格式: x,y):")
        label_position.pack(side=tk.LEFT, padx=5, pady=5)

        entry_position = tk.Entry(position_frame, width=10)
        entry_position.pack(side=tk.LEFT, padx=5, pady=5)

        label_count = tk.Label(position_frame, text="循环次数(0为无限次):")
        label_count.pack(side=tk.LEFT, padx=5, pady=5)

        entry_count = tk.Entry(position_frame, width=5)
        entry_count.pack(side=tk.LEFT, padx=5, pady=5)

        label_interval = tk.Label(position_frame, text="点击间隔(秒):")
        label_interval.pack(side=tk.LEFT, padx=5, pady=5)

        entry_interval = tk.Entry(position_frame, width=5)
        entry_interval.pack(side=tk.LEFT, padx=5, pady=5)

        # 启用复选框
        var_enabled = tk.IntVar(value=1)
        checkbox_enabled = tk.Checkbutton(position_frame, text="启用", variable=var_enabled)
        checkbox_enabled.pack(side=tk.LEFT, padx=5, pady=5)

        # 删除按钮
        btn_delete = tk.Button(position_frame, text="删除", command=lambda: self.delete_position_row(position_frame))
        btn_delete.pack(side=tk.LEFT, padx=5, pady=5)

        self.position_entries.append((entry_description, entry_position, entry_count, entry_interval, var_enabled, position_frame))

        # 调整窗口高度
        self.root.update_idletasks()
        new_height = self.root.winfo_reqheight()
        self.root.geometry(f"{self.root.winfo_width()}x{new_height}")

    def delete_position_row(self, position_frame):

        for widget in position_frame.winfo_children():
            widget.destroy()
        position_frame.destroy()
        self.position_entries = [entry for entry in self.position_entries if entry[-1] != position_frame]

        # 调整窗口高度
        self.root.update_idletasks()
        new_height = self.root.winfo_reqheight()
        self.root.geometry(f"{self.root.winfo_width()}x{new_height}")

    def get_mouse_position(self):
        x, y = pyautogui.position()
        focused_entry = self.root.focus_get()
        if isinstance(focused_entry, tk.Entry):
            focused_entry.delete(0, tk.END)
            focused_entry.insert(0, f"{x},{y}")

    def start_all_clicking(self):
        self.clear_error()

        if not self.position_entries:
            self.show_error("没有设置任何点击位置")
            return

        for entry_description, entry_position, entry_count, entry_interval, var_enabled, _ in self.position_entries:
            if var_enabled.get() == 1:
                description = entry_description.get().strip()
                position = entry_position.get().strip()
                count = int(entry_count.get().strip() or 0)
                interval = float(entry_interval.get().strip() or 0)

                if not description or not position or count < 0 or interval <= 0:
                    self.show_error("请确保所有设置项填写完整")
                    return

        self.btn_start_all.config(state=tk.DISABLED)
        self.btn_stop_all.config(state=tk.NORMAL)

        self.stop_threads = False
        with self.lock:
            self.position_threads.clear()
            for entry_description, entry_position, entry_count, entry_interval, var_enabled, _ in self.position_entries:
                if var_enabled.get() == 1:
                    description = entry_description.get().strip()
                    position = entry_position.get().strip()
                    count = int(entry_count.get().strip() or 0)
                    interval = float(entry_interval.get().strip() or 0)

                    if not description or not position:
                        continue

                    x, y = map(int, position.split(","))

                    thread = threading.Thread(target=self.click_thread, args=(description, x, y, count, interval))
                    self.position_threads.append(thread)
                    thread.start()

    def stop_all_threads(self):
        self.stop_threads = True

        # with self.lock:
        #     threads = self.position_threads[:]
        # for thread in threads:
        #     thread.join()

        with self.lock:
            self.position_threads.clear()

        self.btn_start_all.config(state=tk.NORMAL)
        self.btn_stop_all.config(state=tk.DISABLED)

    def click_thread(self, description, x, y, count, interval):
        alwaysLoop = count == 0
        while not self.stop_threads and (alwaysLoop or count > 0):
            try:
                pyautogui.click(x, y)
            except pyautogui.FailSafeException:
                # 处理 pyautogui 的安全异常
                pass
            count -= 1
            # 添加随机的点击间隔，范围在原始 interval 的基础上浮动
            random_interval = interval / 2.0
            if random_interval > 0.3:
                random_interval = random.uniform(0, 0.3)
            random_interval += interval + random_interval
            time.sleep(random_interval)

    def show_error(self, message):
        self.error_label.config(text=message)

    def clear_error(self):
        self.error_label.config(text="")

    def load_history_to_listbox(self):
        self.history_listbox.delete(0, tk.END)
        for name in self.history_configurations:
            self.history_listbox.insert(tk.END, name)

    def load_selected_history(self):
        selected_index = self.history_listbox.curselection()
        if selected_index:
            selected_name = self.history_listbox.get(selected_index)
            if selected_name in self.history_configurations:
                selected_config = self.history_configurations[selected_name]
                # 清空当前设置
                for entry in self.position_entries:
                    entry[-1].destroy()
                self.position_entries.clear()

                # 加载历史配置
                for description, position, count, interval, enabled in selected_config:
                    self.add_position_row()
                    entry_description, entry_position, entry_count, entry_interval, var_enabled, _ = \
                    self.position_entries[-1]
                    entry_description.insert(0, description)
                    entry_position.insert(0, position)
                    entry_count.insert(0, str(count))
                    entry_interval.insert(0, str(interval))
                    var_enabled.set(enabled)

                # 设置当前描述信息
                self.entry_current_description.delete(0, tk.END)
                self.entry_current_description.insert(0, selected_name)


    def delete_selected_history(self):
        selected_indices  = self.history_listbox.curselection()
        if selected_indices:
            selected_index = selected_indices[0]
            selected_name = self.history_listbox.get(selected_index)
            self.history_listbox.delete(selected_index)
            if selected_name in self.history_configurations:
                del self.history_configurations[selected_name]
                self.save_configurations_to_file()

            # 处理焦点移动
            size = self.history_listbox.size()
            if size > 0:
                if selected_index >= size:
                    selected_index = size - 1
                self.history_listbox.select_set(selected_index)
                self.history_listbox.event_generate("<<ListboxSelect>>")
        else:
            self.show_error("请选择要删除的配置")

    def save_current_configuration(self):
        current_description = self.entry_current_description.get().strip()
        if current_description:
            current_config = []
            for entry_description, entry_position, entry_count, entry_interval, var_enabled, _ in self.position_entries:
                description = entry_description.get().strip()
                position = entry_position.get().strip()
                count = int(entry_count.get().strip() or 0)
                interval = float(entry_interval.get().strip() or 0)
                enabled = var_enabled.get()
                current_config.append((description, position, count, interval, enabled))

            self.history_configurations[current_description] = current_config
            self.save_configurations_to_file()
            self.load_history_to_listbox()
        else:
            self.show_error("当前配置信息描述不能为空")

    def save_current_configuration_background(self):
        current_description = self.entry_current_description.get().strip()
        if current_description:
            current_config = []
            for entry_description, entry_position, entry_count, entry_interval, var_enabled, _ in self.position_entries:
                description = entry_description.get().strip()
                position = entry_position.get().strip()
                count = int(entry_count.get().strip() or 0)
                interval = float(entry_interval.get().strip() or 0)
                enabled = var_enabled.get()
                current_config.append((description, position, count, interval, enabled))

            self.history_configurations[current_description] = current_config
            self.save_configurations_to_file()

    def save_configurations_to_file(self):
        with open("clicker_configurations.json", "w") as file:
            json.dump(self.history_configurations, file, indent=4)

    def load_configurations_from_file(self):
        try:
            with open("clicker_configurations.json", "r") as file:
                self.history_configurations = json.load(file)
        except FileNotFoundError:
            self.history_configurations = {}
        except json.JSONDecodeError:
            self.history_configurations = {}

    def auto_save_timer(self):
        self.save_current_configuration_background()
        self.root.after(60000, self.auto_save_timer)


if __name__ == "__main__":
    root = tk.Tk()
    app = ClickerApp(root)
    root.mainloop()
